#pragma once
#include <string>
#include <memory>
#include "InetAddr.hpp"
#include "log.hpp"
#include "socket.hpp"
#include "LockGuard.hpp"
using namespace socket_ns;
//
class select_server
{
    const static int N = sizeof(fd_set) * 8;
    const static int defaultfd = -1;

public:
    select_server(uint16_t port)
        : _port(port), _listen_sock(std::make_unique<tcp_socket>())
    {
        InetAddr addr("0", _port);
        _listen_sock->build_server_socket(addr);
        for (int i = 0; i < N; i++)
        {
            _fd_array[i] = defaultfd;
        }
        _fd_array[0] = _listen_sock->SockFd();
    }

    void accept_client()
    {
        InetAddr client_addr;
        int sockfd = _listen_sock->accepter(&client_addr);//此时不会阻塞，因为已经有客户端发起连接请求了
        if(sockfd < 0)
        {
            return;
        }
        LOG(DEBUG, "Get new Link, sockfd: %d, client info %s:%d\n", sockfd, client_addr.Ip().c_str(), client_addr.Port());
        int pos = 1;
        //从小的位置开始放文件描述符
        for(; pos < N; pos++)
        {
            if(_fd_array[pos] == defaultfd)
                break;
        }
        if(pos == N)
        {
            ::close(sockfd);
            LOG(WARNING,"server is full! \n");
            return;
        }
        else
        {
            _fd_array[pos] = sockfd;
            LOG(DEBUG, "%d add to select array!\n", sockfd);
        }
        LOG(DEBUG, "curr fd_array[] fd list : %s\n", rfds_to_string().c_str());
    }
    void service_io(int pos)
    {
        char buffer[1024];
        ssize_t n = ::recv(_fd_array[pos],buffer,sizeof(buffer)-1,0);//读取文件描述符内容，不会阻塞，文件内有内容了
        if(n > 0)//读取成功
        {
            buffer[n] = 0;
            std::cout<<"client say:"<<buffer<<std::endl;
            std::string echo_string  = "[sercer echo]# ";
            echo_string += buffer;
            ::send(_fd_array[pos],echo_string.c_str(),echo_string.size(),0);
        }
        else if(n == 0)//文件描述符关闭
        {
            LOG(DEBUG, "%d is closed\n", _fd_array[pos]);
            ::close(_fd_array[pos]);
            _fd_array[pos] = defaultfd;
            LOG(DEBUG, "curr fd_array[] fd list : %s\n", rfds_to_string().c_str());
        }
        else//错误
        {
             LOG(DEBUG, "%d recv error\n", _fd_array[pos]);
            ::close(_fd_array[pos]);
            _fd_array[pos] = defaultfd;
            LOG(DEBUG, "curr fd_array[] fd list : %s\n", rfds_to_string().c_str());
        }
    }
    void handler_event(fd_set& rfds)
    {
        for(int i = 0; i < N; i++)
        {
            if(_fd_array[i] == defaultfd)
                continue;
            //多种情况会导致socket 就绪：需要辨别
            if(FD_ISSET(_fd_array[i], &rfds))//判断_fd_array[i]文件描述符是否还在rfds中，在时说明文件描述符_fs_array[i]有事件需要读
            {
                if(_fd_array[i] == _listen_sock->SockFd())//等于监听套接字时，此时有客户端发起连接请求
                {
                    accept_client();
                }
                else //_fd_array[i]文件描述符中可以读取该文件描述符了
                {
                    service_io(i);
                }
            }
        }
    }
    void loop()
    {
        sleep(10);
        while (true)
        {
            // listensocket 等待新连接到来，等价于对方给我发送数据！我们作为读事件同一处理
            // 新连接到来 等价于 读事件就绪！
            // 首先要将listensock添加到select中！
            fd_set rfds;    // 文件描述符集合
            FD_ZERO(&rfds); // rfds 变量中的所有位都设置为0,初始状态下集合中没有任何一个文件描述符
            int max_fd = defaultfd;
            for (int i = 0; i < N; i++)
            {
                if (_fd_array[i] == defaultfd)
                    continue;
                FD_SET(_fd_array[i], &rfds); // 将所有合法的fd添加到rfds中
                if (max_fd < _fd_array[i])
                {
                    max_fd = _fd_array[i]; // 更新出最大的fd值
                }
            }
            struct timeval timeout = {0, 0};
            // select 同时等待的fd，是有上限的。因为fd_set是具体的数据类型，有自己的大小！
            // rfds是一个输入输出型参数，每次调用，都要对rfds进行重新设定!
            int n = select(max_fd+1, &rfds, nullptr, nullptr, nullptr);
            switch(n)
            {
                case 0:
                    LOG(INFO,"time out, %d:%d\n",timeout.tv_sec,timeout.tv_usec);
                    break;
                case -1:
                    LOG(ERROR, "select error...\n");
                    break;
                default:
                    LOG(DEBUG, "Event Happen. n : %d\n", n); // 底层有一个事件就绪，select为什么会一直通知我？因为：我们没有处理！
                    handler_event(rfds);//处理事件
                    break;
            }
        }
    }
    std::string rfds_to_string()
    {
        std::string fdstr;
        for(int i = 0; i < N; i++)
        {
            if(_fd_array[i] == defaultfd)
            {
                continue;
            }
            fdstr += std::to_string(_fd_array[i]);
            fdstr +=" ";
        }
        return fdstr;
    }
    ~select_server()
    {
    }

private:
    uint16_t _port;                         // 端口号
    std::unique_ptr<mysocket> _listen_sock; // 套接字
    int _fd_array[N];                       // 辅助数组
};
